﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO;
using Tsanie.ShellExtension;
using Tsanie.ShellExtension.Util;

namespace Tsanie.FlvTag {
    public class FlvTagReader {

        #region - Fields -

        private string _filename;
        private long _length;
        private long _seek;
        private uint _duration;
        private double _rate;
        private ScriptTag _metaTag;
        private List<FlvTag> _tags;
        private int _maxTagSize;

        private Predicate<FlvTag> _predicate;
        private Action<long> _maximumGetted;
        private Action<int> _done;

        #endregion

        #region - Properties -

        public Predicate<FlvTag> Predicate { set { _predicate = value; } }
        public Action<long> MaximumGetted { set { _maximumGetted = value; } }
        public Action<int> Done { set { _done = value; } }
        public long Length { get { return _length; } }
        public long Seek { get { return _seek; } }
        public uint Duration { get { return _duration; } }
        public double Rate { get { return _rate; } }
        public List<FlvTag> Tags { get { return _tags; } }
        public string Filename { get { return _filename; } }
        public ScriptTag MetaTag { get { return _metaTag; } }
        public int MaxTagSize { get { return _maxTagSize; } }

        #endregion

        public FlvTagReader(string filename) {
            _filename = filename;
            _length = 0;
            _seek = 0;
            _duration = 0;
            _rate = 0;
            _metaTag = null;
            _tags = new List<FlvTag>();
            _predicate = (t) => true;
        }

        #region - Public Methods -

        public Thread StartRead() {
            if (_filename == null)
                return null;
            FileStream fs = new FileStream(_filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            _length = fs.Length;
            _maximumGetted.SafeRun(_length);
            FLVHeader header = FLVHeader.ReadHeader(fs);
            if (!header.IsFlv)
                return null;
            Thread thread = new Thread(delegate() {
                try {
                    fs.Seek(header.Length, SeekOrigin.Begin);
                    _tags.Clear();
                    FlvTag tag;
                    while ((tag = FlvTag.ReadTag(fs)) != null) {
                        if (tag is ScriptTag)
                            _metaTag = tag as ScriptTag;
                        _tags.Add(tag);
                        if (_duration < tag.TimeStamp)
                            _duration = tag.TimeStamp;
                        if (_maxTagSize < tag.DataSize)
                            _maxTagSize = tag.DataSize;
                        _seek += tag.DataSize;
                        if (!_predicate.SafeRun(tag))
                            break;
                    }
                    if (_tags.Count > 1) {
                        _length = _length - _tags[1].Offset + 11;
                        _rate = (_duration == 0 ? 0 : _length * 8 / _duration);
                    }
#if TRACE
                    Common.OutputDebugString("StartRead(void):: read {0} tag(s).", _tags.Count);
#endif
                } catch (Exception e) {
                    Common.OutputDebugString("StartRead(void):: {0} - {1}", e.GetType().FullName, e.Message);
                } finally {
                    fs.Close();
                    fs = null;
                    _done.SafeRun(_tags.Count);
                }
            });
            thread.Name = "thread_ReadFile_" + _filename;
            thread.Start();
            return thread;
        }
        public void Remove(int index) {
            if (index < 0 || index >= _tags.Count)
                return;
            FlvTag tag = _tags[index];
            if (_duration <= tag.TimeStamp)
                RefreshDuration();
            _length -= tag.DataSize + 11;
            _tags.RemoveAt(index);
        }
        public void RefreshDuration() {
            uint max = 0;
            foreach (FlvTag t in _tags)
                if (max < t.TimeStamp)
                    max = t.TimeStamp;
            _duration = max;
            _rate = (_duration == 0 ? 0 : _length * 8 / _duration);
        }

        #endregion

    }

    class FLVHeader {
        private byte[] signature;
        private byte version;
        private byte typeflag;
        private int dataoffset;

        private FLVHeader() {
            signature = new byte[3];
            version = 0;
            typeflag = 0;
            dataoffset = 0;
        }
        public static FLVHeader ReadHeader(Stream stream) {
            FLVHeader header = new FLVHeader();
            byte[] buffer = new byte[4];
            stream.Read(header.signature, 0, 3);
            stream.Read(buffer, 0, 1);
            header.version = buffer[0];
            stream.Read(buffer, 0, 1);
            header.typeflag = buffer[0];
            try {
                header.dataoffset = (int)stream.ReadUI32();
            } catch { }
            return header;
        }

        public bool IsFlv {
            get {
                if (signature == null || signature.Length != 3)
                    return false;
                // 'FLV'
                return (signature[0] == 0x46) && (signature[1] == 0x4C) && (signature[2] == 0x56);
            }
        }
        public int Version { get { return version; } }
        public bool HasVideo { get { return (typeflag & 0x1) == 0x1; } }
        public bool HasAudio { get { return (typeflag & 0x4) == 0x4; } }
        public int Length { get { return dataoffset; } }
    }

}
